﻿/*
 * This file is part of MonoSettlers.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://opensettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace MonoSettlers
{
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
        public int X;
        public int Y;
    }

    internal unsafe class API32
    {
        [DllImport("FastPathFinder32", CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr PfCreate(
            int* inGrid,
            int inSize);

        [DllImport("FastPathFinder32", CallingConvention = CallingConvention.StdCall)]
        public static extern void PfBeginInstancing(IntPtr inPathFinder, ref POINT inStart, ref POINT inEnd, int inContext);

        [DllImport("FastPathFinder32", CallingConvention = CallingConvention.StdCall)]
        public static extern double PfGetGValue(IntPtr inPathFinder, ref POINT inForCell);

        [DllImport("FastPathFinder32", CallingConvention = CallingConvention.StdCall)]
        public static extern unsafe IntPtr PfSearch(IntPtr inPathFinder, ref POINT inStart, ref POINT inEnd, int inContext, out int outResultCount);

        [DllImport("FastPathFinder32", CallingConvention = CallingConvention.StdCall)]
        public static extern void PfRelease(IntPtr inPathFinder);

        [DllImport("FastPathFinder32", CallingConvention = CallingConvention.StdCall)]
        public static extern void PfReleaseResult(IntPtr inResult);
    }

    internal unsafe class API64
    {
        [DllImport("FastPathFinder64", CallingConvention = CallingConvention.StdCall)]
        public static extern IntPtr PfCreate(
            int* inGrid,
            int inSize);

        [DllImport("FastPathFinder64", CallingConvention = CallingConvention.StdCall)]
        public static extern void PfBeginInstancing(IntPtr inPathFinder, ref POINT inStart, ref POINT inEnd, int inContext);

        [DllImport("FastPathFinder64", CallingConvention = CallingConvention.StdCall)]
        public static extern double PfGetGValue(IntPtr inPathFinder, ref POINT inForCell);

        [DllImport("FastPathFinder64", CallingConvention = CallingConvention.StdCall)]
        public static extern unsafe IntPtr PfSearch(IntPtr inPathFinder, ref POINT inStart, ref POINT inEnd, int inContext, out int outResultCount);

        [DllImport("FastPathFinder64", CallingConvention = CallingConvention.StdCall)]
        public static extern void PfRelease(IntPtr inPathFinder);

        [DllImport("FastPathFinder64", CallingConvention = CallingConvention.StdCall)]
        public static extern void PfReleaseResult(IntPtr inResult);
    }

    internal unsafe class Native_AStarSolver<TPathNode> : IAStarSolver<TPathNode> where TPathNode : IAStarNode
    {

        private GCHandle? m_hGrid = null;
        private int[] m_NativeGrid;
        private IntPtr m_Handle = IntPtr.Zero;

        public TPathNode[,] SearchSpace { get; private set; }

        public Native_AStarSolver(TPathNode[,] inGrid)
        {
            Width = inGrid.GetLength(0);
            Height = inGrid.GetLength(1);
            SearchSpace = inGrid;

            int sizeShift = (int)Math.Floor((Math.Log((double)Width) * MovableManager.LOG_2_FACTOR) + 0.5);

	        if((int)Math.Pow(2, (double)sizeShift) != Width)
		        throw new ApplicationException("Grid size must be a power of two.");

            if(Width != Height)
                throw new ApplicationException("Grid must be a square.");

            m_NativeGrid = new int[Width * Height];
            m_hGrid = GCHandle.Alloc(m_NativeGrid, GCHandleType.Pinned);

            if (IntPtr.Size == 8)
            {
                m_Handle = API64.PfCreate((int*)m_hGrid.Value.AddrOfPinnedObject().ToPointer(), Width);
            }
            else
            {
                m_Handle = API32.PfCreate((int*)m_hGrid.Value.AddrOfPinnedObject().ToPointer(), Width);
            }
        }


        public int Width { get; private set; }
        public int Height { get; private set; }

        public void BeginSearch(Point inStartNode, Point inEndNode, int inUserContext)
        {
            POINT startNode = new POINT()
            {
                X = inStartNode.X,
                Y = inStartNode.Y,
            };
            POINT endNode = new POINT()
            {
                X = inEndNode.X,
                Y = inEndNode.Y,
            };

            if (IntPtr.Size == 8)
            {
                API64.PfBeginInstancing(m_Handle, ref startNode, ref endNode, inUserContext);
            }
            else
            {
                API32.PfBeginInstancing(m_Handle, ref startNode, ref endNode, inUserContext);
            }
        }

        public double GetClosedGValue(Point inClosedNode)
        {
            POINT closedNode = new POINT()
            {
                X = inClosedNode.X,
                Y = inClosedNode.Y,
            };

            if (IntPtr.Size == 8)
            {
                return API64.PfGetGValue(m_Handle, ref closedNode);
            }
            else
            {
                return API32.PfGetGValue(m_Handle, ref closedNode);
            }
        }

        public bool IsWalkable(int inX, int inY, int inContext)
        {
            return inContext >= SearchSpace[inX, inY].WallValue;
        }
    }
}
